using System.Diagnostics.CodeAnalysis;

namespace Bread.Utility;

public struct RectD : IEquatable<RectD>, IParsable<RectD>
{
    /// <summary>
    /// (0, 0, 0, 0)
    /// </summary>
    public static RectD Empty => new RectD();

    /// <summary>
    /// (0, 0, 1, 1)
    /// </summary>
    public static RectD Identity => new RectD(0, 0, 1, 1);

    public double X { get; set; } = 0;

    public double Y { get; set; } = 0;

    public double Width { get; set; } = 0;

    public double Height { get; set; } = 0;

    public double Top => Y;

    public double Left => X;

    public double Right => X + Width;

    public double Bottom => Y + Height;

    public double W => Width;

    public double H => Height;

    public bool IsEmpty => Width < Epsilon || Height < Epsilon;

    private static double Epsilon = 0.0000001;

    public RectD()
    {
    }

    public RectD(double x, double y, double width, double height)
    {
        X = x;
        Y = y;
        Width = width;
        Height = height;
    }

    /// <summary>
    /// if it is (0, 0, 1.0, 1.0)
    /// </summary>
    /// <returns></returns>
    public bool IsIdentity()
    {
        if (Math.Abs(X) > Epsilon) return false;
        if (Math.Abs(Y) > Epsilon) return false;
        if (Math.Abs(Width - 1.0) > Epsilon) return false;
        if (Math.Abs(Height - 1.0) > Epsilon) return false;
        return true;
    }

    /// <summary>
    /// return (0, 0, 1.0, 1.0) if invalid, otherwise return itself
    /// </summary>
    /// <returns></returns>
    public RectD IdentityIfInvalid()
    {
        if (Width < Epsilon) return new RectD(0, 0, 1.0, 1.0);
        if (Height < Epsilon) return new RectD(0, 0, 1.0, 1.0);
        return this;
    }

    public bool Contains(double x, double y)
    {
        if (x < X) return false;
        if (x > X + Width) return false;
        if (y < Y) return false;
        if (y > Y + Height) return false;
        return true;
    }

    public bool Contains(PointD p)
    {
        if (p.X < X) return false;
        if (p.X > X + Width) return false;
        if (p.Y < Y) return false;
        if (p.Y > Y + Height) return false;
        return true;
    }

    public RectD Clone()
    {
        return new RectD(X, Y, Width, Height);
    }

    public RectD Clip(double dstW = 1.0, double dstH = 1.0)
    {
        var l = X < 0 ? 0 : X;
        var r = X + Width > dstW ? dstW : X + Width;
        var t = Y < 0 ? 0 : Y;
        var b = Y + Height > dstH ? dstH : Y + Height;
        return new RectD(l, t, r - l, b - t);
    }



    public RectD Intersect(RectD rc)
    {
        var x1 = Math.Max(X, rc.X);
        var x2 = Math.Min(Right, rc.Right);
        var y1 = Math.Max(Y, rc.Y);
        var y2 = Math.Min(Bottom, rc.Bottom);

        if (x2 > x1 && y2 > y1) {
            return new RectD(x1, y1, x2 - x1, y2 - y1);
        }
        return Empty;
    }

    public bool IsIntersectWith(RectD rect)
    {
        return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
    }

    public RectD Union(RectD rect)
    {
        if (Width == 0 && Height == 0) {
            return rect;
        }
        else if (rect.Width == 0 && rect.Height == 0) {
            return this;
        }
        else {
            var x1 = Math.Min(X, rect.X);
            var x2 = Math.Max(Right, rect.Right);
            var y1 = Math.Min(Y, rect.Y);
            var y2 = Math.Max(Bottom, rect.Bottom);
            return new RectD(x1, y1, x2 - x1, y2 - y1);
        }
    }

    internal static RectD? Union(RectD? left, RectD? right)
    {
        if (left == null)
            return right;
        if (right == null)
            return left;
        return left.Value.Union(right.Value);
    }

    public void Offset(double x, double y)
    {
        this.X += x;
        this.Y += y;
    }

    public RectD Translate(double x, double y)
    {
        return new RectD(X + x, Y + y, Width, Height);
    }

    /// <summary>
    /// 将像素坐标转换为无量纲坐标
    /// </summary>
    public RectD Normalize(double w, double h)
    {
        return new RectD(X / w, Y / h, Width / w, Height / h);
    }

    /// <summary>
    /// 将无量纲坐标转换为像素坐标
    /// </summary>
    public RectD UnNormalize(double w, double h)
    {
        return new RectD(X * w, Y * h, Width * w, Height * h);
    }

    public RectN ToRectN()
    {
        return new RectN((int)X, (int)Y, (int)Width, (int)Height);
    }

    public static RectD FromRectN(RectN rc)
    {
        return new RectD(rc.X, rc.Y, rc.Width, rc.Height);
    }

    /// <summary>
    /// 根据旋转后的结果逆推旋转前的位置<br/>
    /// 必须在clip和normalize之后调用
    /// </summary>
    public RectD UnRotation(int rotation)
    {
        if (rotation == 1) {
            // 顺时针旋转了90°
            return new RectD(Y, 1 - X - W, H, W);
        }

        if (rotation == 2) {
            // 顺时针旋转了180°
            return new RectD(1 - X - W, 1 - Y - H, W, H);
        }

        if (rotation == 3) {
            // 顺时针旋转了270°
            return new RectD(1 - Y - H, X, H, W);
        }

        return Clone();
    }

  

    public bool Equals(RectD o)
    {
        if (Math.Abs(o.X - X) > Epsilon) return false;
        if (Math.Abs(o.Y - Y) > Epsilon) return false;
        if (Math.Abs(o.W - W) > Epsilon) return false;
        if (Math.Abs(o.H - H) > Epsilon) return false;
        return true;
    }

    public override bool Equals(object? obj) => obj is RectD other && Equals(other);

    public override int GetHashCode()
    {
        unchecked {
            int hash = 17;
            hash = (hash * 23) + X.GetHashCode();
            hash = (hash * 23) + Y.GetHashCode();
            hash = (hash * 23) + Width.GetHashCode();
            hash = (hash * 23) + Height.GetHashCode();
            return hash;
        }
    }

    public override string ToString()
    {
        return $"({X},{Y},{W},{H})";
    }

    public static RectD Parse(string s, IFormatProvider? provider)
    {
        _ = TryParse(s, provider, out var result);
        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out RectD result)
    {
        result = Empty;

        if (s == null) return false;
        if (s.StartsWith('(') == false) return false;
        if (s.EndsWith(')') == false) return false;
        if (s.Length < 9) return false;
        var splits = s.Substring(1, s.Length - 2).Split(',');
        if (splits.Length != 4) return false;

        if (double.TryParse(splits[0], out var x) == false) return false;
        if (double.TryParse(splits[1], out var y) == false) return false;
        if (double.TryParse(splits[2], out var w) == false) return false;
        if (double.TryParse(splits[3], out var h) == false) return false;

        result.X = x;
        result.Y = y;
        result.Width = w;
        result.Height = h;
        return true;
    }




    #region Operater

    /// <summary>
    /// Checks for equality between two <see cref="RectD"/>s.
    /// </summary>
    /// <param name="left">The first rect.</param>
    /// <param name="right">The second rect.</param>
    /// <returns>True if the rects are equal; otherwise false.</returns>
    public static bool operator ==(RectD left, RectD right)
    {
        return left.Equals(right);
    }

    /// <summary>
    /// Checks for inequality between two <see cref="RectD"/>s.
    /// </summary>
    /// <param name="left">The first rect.</param>
    /// <param name="right">The second rect.</param>
    /// <returns>True if the rects are unequal; otherwise false.</returns>
    public static bool operator !=(RectD left, RectD right)
    {
        return !(left == right);
    }


    /// <summary>
    /// Multiplies a rectangle by a scale.
    /// </summary>
    /// <param name="rect">The rectangle.</param>
    /// <param name="scale">The scale.</param>
    /// <returns>The scaled rectangle.</returns>
    public static RectD operator *(RectD rect, double scale)
    {
        return new RectD(
            rect.X * scale,
            rect.Y * scale,
            rect.Width * scale,
            rect.Height * scale);
    }

    #endregion

}
